// Copyright (c) 2011 Blue Onion Software, All rights reserved
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Net.Mail;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using BlueOnionSoftware.Bloget.Properties;

namespace BlueOnionSoftware.Bloget
{
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces"), DefaultProperty("DataFile")]
    public abstract class Bloget : CompositeControl
    {
        private string _dataContext;
        private string _prefix;

        private Blog _blog;
        private Mode _mode;

        private string _queryPost;
        private int _queryStart;
        private Category _queryCategory;
        private string _querySort;
        private DateTime _queryDate;

        internal const string AdministratorRole = "BlogAdministrator";
        internal const string AuthorRole = "BlogAuthor";

        [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.DateTime.TryParse(System.String,System.DateTime@)"), SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#")]
        protected override void OnInit(EventArgs e)
        {
            if (string.IsNullOrEmpty(DataContext))
            {
                ErrorPage("No DataFile Specified");
                return;
            }

            EnableViewState = false;

            try
            {
                var path = Page.MapPath(DataContext);
                _blog = Blog.Load(path);
            }

            catch (Exception ex)
            {
                ErrorPage(ex.ToString());
                return;
            }

            _queryPost = Page.Request[BlogPostParameter] ?? string.Empty;

            if (int.TryParse((Page.Request[BlogStartParameter] ?? "0"), out _queryStart) == false)
            {
                _queryStart = 0;
            }

            _queryCategory = CategoryFromIdString(Page.Request[BlogCategoryParameter]);
            _querySort = Page.Request[BlogSortParameter] ?? string.Empty;
            _querySort = Page.Server.HtmlDecode(_querySort);
            DateTime.TryParse(Page.Request[BlogDateParameter], out _queryDate);
            _mode = string.IsNullOrEmpty(_queryPost) ? Mode.Blog : Mode.Post;
            var modeParameter = Page.Request[BlogModeParameter];

            if (string.IsNullOrEmpty(modeParameter) == false)
            {
                try
                {
                    _mode = (Mode) Enum.Parse(typeof (Mode), modeParameter, true);
                }

                catch (ArgumentException)
                {
                    _mode = Mode.Blog;
                }
            }

            if (ShowOnly != Mode.None)
            {
                _mode = ShowOnly;
            }

            if (_blog.LicenseAgreement == false)
            {
                _mode = Mode.LicenseAgreement;
            }

            DataBind();
            base.OnInit(e);
        }

        [Bindable(false)]
        [Category("Data")]
        [DefaultValue("")]
        [Localizable(false)]
        public string DataContext
        {
            get { return _dataContext ?? string.Empty; }
            set { _dataContext = value; }
        }

        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue("Mode.None")]
        [Localizable(false)]
        public Mode ShowOnly { get; set; }

        [Bindable(false)]
        [Category("Behavior")]
        [Localizable(false)]
        public string Prefix
        {
            get { return _prefix ?? string.Empty; }
            set { _prefix = value; }
        }

        [Bindable(false)]
        [Browsable(false)]
        public MembershipUser GetUser()
        {
            return Membership.GetUser();
        }

        [Bindable(false)]
        [Browsable(false)]
        public bool IsLoggedIn
        {
            get { return (GetUser() != null); }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal bool IsAdministrator
        {
            get
            {
                var user = GetUser();
                return (user != null) ? Roles.IsUserInRole(AdministratorRole) : false;
            }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal bool IsAuthor
        {
            get
            {
                var user = GetUser();
                return (user != null) ? Roles.IsUserInRole(AuthorRole) : false;
            }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal bool IsAuthorOrAdministrator
        {
            get { return IsAuthor || IsAdministrator; }
        }

        internal static bool IsUserAdministrator(MembershipUser user)
        {
            return Roles.IsUserInRole(user.UserName, AdministratorRole);
        }

        internal static bool IsUserAuthor(MembershipUser user)
        {
            return Roles.IsUserInRole(user.UserName, AuthorRole);
        }

        internal static MembershipUserCollection GetAllUsers()
        {
            return Membership.GetAllUsers();
        }

        [Bindable(false)]
        [Browsable(false)]
        public string BlogTitle
        {
            get { return _blog.Title; }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal Blog Blog
        {
            get { return _blog; }
        }

        [Bindable(false)]
        [Browsable(false)]
        public Mode Mode
        {
            get { return _mode; }
        }

        [Bindable(false)]
        [Browsable(false)]
        public string QueryPost
        {
            get { return _queryPost; }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal int QueryStart
        {
            get { return _queryStart; }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal Category QueryCategory
        {
            get { return _queryCategory; }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal string QuerySort
        {
            get { return _querySort ?? string.Empty; }
        }

        [Bindable(false)]
        [Browsable(false)]
        internal DateTime QueryDate
        {
            get { return _queryDate; }
        }

        [Bindable(false)]
        [Browsable(false)]
        public Post GetPost(Guid postId)
        {
            return Blog.Posts[postId];
        }

        internal void PoweredByBloget()
        {
            var tagline = string.Format(CultureInfo.CurrentCulture,
                                        "<p style=\"font-size:x-small; text-align:center\">{0} " +
                                            "<a href=\"http://blueonionsoftware.com/Bloget.aspx\">{1}&trade;</a></p>",
                                        Resources.PoweredBy, Resources.BlogetTitle);

            Controls.Add(new LiteralControl(tagline));
        }

        internal void ErrorPage(string error)
        {
            _mode = Mode.None;
            ChildControlsCreated = true;
            Controls.Add(new LiteralControl("<p>" + error + "</p>"));
        }

        internal string BlogModeParameter
        {
            get { return Prefix + "m"; }
        }

        internal string BlogPostParameter
        {
            get { return Prefix + "p"; }
        }

        internal string BlogStartParameter
        {
            get { return Prefix + "s"; }
        }

        internal string BlogCategoryParameter
        {
            get { return Prefix + "c"; }
        }

        internal string BlogSortParameter
        {
            get { return Prefix + "o"; }
        }

        internal string BlogDateParameter
        {
            get { return Prefix + "d"; }
        }

        internal string BlogSearchParameter
        {
            get { return Prefix + "f"; }
        }

        internal string BlogUserParameter
        {
            get { return Prefix + "u"; }
        }

        internal string BuildQueryString(Mode modeArg)
        {
            return BuildQueryString(modeArg, null, 0, null, null, default(DateTime), null);
        }

        [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
        internal string BuildQueryString(Mode modeArg,
                                         Post post,
                                         int start,
                                         Category category,
                                         string sortExpression,
                                         DateTime date,
                                         string userName)
        {
            var link = Page.Request.Url.ToString();
            var offset = link.IndexOf('?');

            if (offset != -1)
            {
                link = link.Substring(0, offset);
            }

            var query = new StringBuilder();
            var argSeparator = "?";

            query.Append(link);

            if (post != null)
            {
                query.Append(argSeparator);
                query.Append(BlogPostParameter);
                query.Append("=");
                query.Append(post.Id.ToString());
                argSeparator = "&";
            }

            if (modeArg != Mode.Blog && modeArg != Mode.Post)
            {
                query.Append(argSeparator);
                query.Append(BlogModeParameter);
                query.Append("=");
                query.Append(modeArg.ToString().ToLowerInvariant());
                argSeparator = "&";
            }

            if (start != 0)
            {
                query.Append(argSeparator);
                query.Append(BlogStartParameter);
                query.Append("=");
                query.Append(start.ToString(CultureInfo.InvariantCulture));
                argSeparator = "&";
            }

            if (category != null)
            {
                query.Append(argSeparator);
                query.Append(BlogCategoryParameter);
                query.Append("=");
                query.Append(category.Id.ToString(CultureInfo.InvariantCulture));
                argSeparator = "&";
            }

            if (string.IsNullOrEmpty(sortExpression) == false)
            {
                query.Append(argSeparator);
                query.Append(BlogSortParameter);
                query.Append("=");
                query.Append(Page.Server.UrlEncode(sortExpression));
                argSeparator = "&";
            }

            if (string.IsNullOrEmpty(userName) == false)
            {
                query.Append(argSeparator);
                query.Append(BlogUserParameter);
                query.Append("=");
                query.Append(Page.Server.UrlEncode(userName));
                argSeparator = "&";
            }

            if (date != default(DateTime))
            {
                query.Append(argSeparator);
                query.Append(BlogDateParameter);
                query.Append("=");
                query.Append(date.ToString("MM/yyyy", CultureInfo.InvariantCulture));
                argSeparator = "&";
            }

            // Propagate other query parameters
            foreach (string key in Page.Request.QueryString)
            {
                if (string.CompareOrdinal(key, BlogModeParameter) == 0 ||
                    string.CompareOrdinal(key, BlogPostParameter) == 0 ||
                        string.CompareOrdinal(key, BlogStartParameter) == 0 ||
                            string.CompareOrdinal(key, BlogCategoryParameter) == 0 ||
                                string.CompareOrdinal(key, BlogSortParameter) == 0 ||
                                    string.CompareOrdinal(key, BlogDateParameter) == 0 ||
                                        string.CompareOrdinal(key, BlogUserParameter) == 0)
                {
                    continue;
                }

                query.Append(argSeparator);
                query.Append(key);
                query.Append("=");
                query.Append(Page.Request.QueryString[key]);
                argSeparator = "&";
            }

            return query.ToString();
        }

        internal string PermaLink(Post post)
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}?{1}={2}",
                                 _blog.Link, BlogPostParameter, post.Id);
        }

        internal string RpcServiceUrl()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}?{1}={2}",
                                 _blog.Link, BlogModeParameter, Mode.Rpc);
        }

        private Category CategoryFromIdString(string id)
        {
            int categoryId;
            Category category = null;

            if (int.TryParse(id, out categoryId))
            {
                if (_blog.Categories.Contains(categoryId))
                {
                    category = _blog.Categories[categoryId];
                }
            }

            return category;
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        internal void SendEmail(string to, string subject, string message)
        {
            using (var mailMessage = new MailMessage())
            {
                try
                {
                    mailMessage.To.Add(new MailAddress(to));
                    mailMessage.Subject = subject;
                    mailMessage.Body = message;

                    var smtp = new SmtpClient();
                    smtp.Send(mailMessage);
                    Log.EmailSent(this, mailMessage.From.Address, to, subject);
                }

                catch (Exception ex)
                {
                    Log.Exception(this, "SendEmail", ex);
                }
            }
        }

        internal HyperLink[] RelatedPosts(Post post, int maxPosts)
        {
            if (post == null)
            {
                throw new ArgumentNullException("post");
            }
            var tags = (from tagInt in post.Tags
                        where _blog.Categories.Contains(tagInt)
                        select _blog.Categories[tagInt].Name).ToList();

            var query = new StringBuilder();

            foreach (var tag in tags)
            {
                const string space = " ";
                query.Append(tag);
                query.Append(space);
            }

            var posts = Search.Find(this, query.Append(post.Title).ToString());
            var links = new List<HyperLink>();

            foreach (var matchingPost in posts)
            {
                if (matchingPost != post)
                {
                    var link = new HyperLink
                    {
                        Text = matchingPost.Title,
                        NavigateUrl = BuildQueryString(Mode.Post, matchingPost, 0, null, null, default(DateTime), null)
                    };
                    links.Add(link);
                }
                if (--maxPosts < 0)
                {
                    break;
                }
            }

            return links.ToArray();
        }

        internal void Redirect(string redirectUrl)
        {
            redirectUrl = Page.Response.ApplyAppPathModifier(redirectUrl);
            Page.Response.Redirect(redirectUrl, true);
        }

        internal void Write(string html)
        {
            Throw.IfNull(html, "html");
            Controls.Add(new LiteralControl(html));
        }

        internal void WriteLine(string html)
        {
            Throw.IfNull(html, "html");
            Controls.Add(new LiteralControl(html + Environment.NewLine));
        }

        [Browsable(false)]
        [Description("Header template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (BlogContainerControl))]
        public ITemplate TitleTemplate { get; set; }

        [Browsable(false)]
        [Description("Footer template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (BlogContainerControl))]
        public ITemplate FooterTemplate { get; set; }

        [Browsable(false)]
        [Description("Post template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate PostTemplate { get; set; }

        [Browsable(false)]
        [Description("Post separator template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate PostSeparatorTemplate { get; set; }

        [Browsable(false)]
        [Description("Comment header template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate CommentHeaderTemplate { get; set; }

        [Browsable(false)]
        [Description("Comment template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (CommentContainerControl))]
        public ITemplate CommentTemplate { get; set; }

        [Browsable(false)]
        [Description("Pingback header template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate PingBackHeaderTemplate { get; set; }

        [Browsable(false)]
        [Description("Pingback template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PingBackContainerControl))]
        public ITemplate PingBackTemplate { get; set; }

        [Browsable(false)]
        [Description("Index header template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (IndexContainerControl))]
        public ITemplate IndexHeaderTemplate { get; set; }

        [Browsable(false)]
        [Description("Index template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (IndexContainerControl))]
        public ITemplate IndexTemplate { get; set; }

        [Browsable(false)]
        [Description("Index month template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (IndexContainerControl))]
        public ITemplate IndexMonthSeparatorTemplate { get; set; }

        [Browsable(false)]
        [Description("Index year template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (IndexContainerControl))]
        public ITemplate IndexYearSeparatorTemplate { get; set; }

        [Browsable(false)]
        [Description("Index footer template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (IndexContainerControl))]
        public ITemplate IndexFooterTemplate { get; set; }

        [Browsable(false)]
        [Description("RSS content template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate RssContentTemplate { get; set; }

        [Browsable(false)]
        [Description("Search template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (BlogContainerControl))]
        public ITemplate SearchTemplate { get; set; }

        [Browsable(false)]
        [Description("Search results template")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof (PostContainerControl))]
        public ITemplate SearchResultsTemplate { get; set; }
    }
}